<?php
/**
 * 描述 : 提供网络通信相关封装
 * 作者 : Edgar.lee
 */
class uc_api_system {
    private static $params = null;                                                                                      //默认请求参数
    private static $cookie = array();                                                                                   //静态cookie

    /**
     * 描述 : 初始化
     * 作者 : Edgar.lee
     */
    public static function init() {
        self::$params = array(
            'scheme' => empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https',
            'host'   => empty($_SERVER['HTTP_HOST']) ? '127.0.0.1' : $_SERVER['HTTP_HOST'],
            'path'   => $_SERVER['SCRIPT_NAME'],
            'port'   => empty($_SERVER['SERVER_PORT']) ? '80' : $_SERVER['SERVER_PORT'],
            'query'  => ''
        );
    }

    /**
     * 描述 : 数据请求
     * 参数 :
     *      url  : 请求的完整路径(非完整路径将用当前站数据填充)
     *      data : 提交数据,含有如下结构的数组,其数据可以是字符串或数组 {
     *          "get"     : get数据(''),
     *          "post"    : post数据(''),
     *          "cookie"  : cookie数据(''),
     *          "timeout" : 超时时间(10s)
     *      }
     *      mode : 提交模式,
     *          false(默认) = 同步提交,
     *          true        = 无结果异步提交,
     *          字符串      = 编译并调用方(变量$_接收响应数据),
     *          数组        = {'callback' : 符合可调用规范, 'params' : 回调参数}, 仅能传递动态参数,不能传递静态函数或资源文件
     * 返回 :
     *      失败时 : {state:false, errno:错误描述, errstr:失败码}
     *      成功时 : {state:true, header:响应头, response:响应数据}
     * 作者 : Edgar.lee
     */
    public static function &request($url = null, $data = array(), $mode = false) {
        ini_set('max_execution_time', 0);                                                                                           //永不超时
        if( $url === null )                                                                                                         //二次请求
        {
            if( $_SERVER['REMOTE_ADDR'] === $_SERVER['SERVER_ADDR'] )                                                               //由本机IP请求
            {
                ignore_user_abort(true);                                                                                            //忽略客户端断开
                session_write_close();                                                                                              //默认关闭session
                $data = unserialize(file_get_contents('php://input'));                                                              //读取post数据
                $mode = &$data['mode'];
                self::$cookie = &$data['staticCookie'];
                unset($data['staticCookie'], $data['mode']);

                $index = &self::request($data['url'], $data, false);
                if( $mode !== true ) {                                                                                              //字符串 或 数组
                    is_string($mode) && $mode = array('callback' => create_function('&$_', $mode));                                 //创建方法
                    $mode['params']['_'] = &$index;                                                                                 //参数引用
                    call_user_func_array($mode['callback'], $mode['params']);
                }
            } else {
                trigger_error("Suspected malicious requests");                                                                      //怀疑恶意请求
            }
            return $url;
        }

        if( $mode === false )                                                                                                       //同步请求
        {
            $data += array('get' => '', 'post' => '', 'cookie' => '', 'header' => '', 'timeout' => 10);                             //参数初始化
            is_array($data['get']) && $data['get'] = http_build_query($data['get']);                                                //格式化get参数
            is_array($data['post']) && $data['post'] = http_build_query($data['post']);                                             //格式化post参数
            is_array($data['header']) && $data['header'] = join("\r\n", $data['header']);                                           //格式化header参数

            $data['url'] = parse_url($url);                                                                                         //解析目标网址
            isset($data['url']['host']) && empty($data['url']['port']) && $data['url']['port'] = '80';                              //外网 && 默认端口 && 赋值80
            $data['url'] += self::$params;                                                                                          //补全参数
            $data['url']['query'] .= ($data['url']['query'] === '' || $data['get'] === '' ? '' : '&') . $data['get'];               //合并get参数

            if( $data['cookie'] ) {                                                                                                 //cookie整合
                is_array($data['cookie']) && $data['cookie'] = http_build_query($data['cookie'], '', '; ');
                $data['cookie'] = explode('; ', $data['cookie']);
                foreach($data['cookie'] as &$v) {
                    $temp = explode('=', $v, 2);
                    self::cookie(array(
                            'domain' => &$data['url']['host'],
                            'path'   => '/',                                                                                            //直传的cookie设置为"/"
                            'name'   => &$temp[0],
                            'value'  => &$temp[1],
                            'encode' => false
                        ));
                }
            }
            $data['cookie'] = self::cookie(array(                                                                                   //读取cookie
                    'domain' => &$data['url']['host'],
                    'path'   => self::realpath('/' . $data['url']['path'] . 'a/../')
                ));
        } else {                                                                                                                    //准备二次请求
            $data['mode'] = $mode;                                                                                                  //二次请求模式
            $data['url'] = $url;                                                                                                    //保存请求网址
            $data['staticCookie'] = &self::$cookie;                                                                                 //静态cookie

            $mode = true;
            $data = array(
                'post'    => serialize($data),
                'cookie'  => session_name() .'='. session_id() . (isset($_SERVER['HTTP_COOKIE']) ? '; ' . $_SERVER['HTTP_COOKIE'] : ''),
                'url'     => self::$params,
                'timeout' => 30
            );
            $data['url']['path'] = OF_URL . '/index.php';
            $data['url']['query'] = 'a=request&c=uc_api_system';
        }

        $fp = fsockopen(strtolower($data['url']['scheme']) === 'https' ? 'ssl://' : '' . gethostbyname($data['url']['host']), $data['url']['port'], $errno, $errstr, $data['timeout']);
        if ( $fp )                                                                                                                  //连接成功
        {
            $out[] = (empty($data['post']) ? 'GET' : 'POST') . " {$data['url']['path']}?{$data['url']['query']} HTTP/1.1\r\n";
            $out[] = "Host: {$data['url']['host']}\r\n";
            empty($data['header']) || $out[] = $data['header'] . "\r\n";                                                            //会替换下面的默认值
            $out[] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";                                 //支持的 MIME 类型
            $out[] = "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0\r\n";                    //使用的浏览器
            $out[] = "Content-type: application/x-www-form-urlencoded\r\n";                                                         //通知接收post值
            $out[] = "Connection: Close\r\n";                                                                                       //禁止缓存
            $out[] = "Accept-Encoding: none\r\n";                                                                                   //禁止压缩
            $out[] = "Cookie: {$data['cookie']}\r\n";                                                                               //发送cookie
            $out[] = 'Content-Length: ' .strlen($data['post']). "\r\n";                                                             //post数据长度
            $out[] = "\r\n";
            $out[] = "{$data['post']}\r\n";                                                                                         //post数据
            fwrite($fp, join($out));

            $res = null;
            if( $mode === false )                                                                                                   //同步调用
            {
                while ( !feof($fp) )
                {
                    if( ($receive[] = fgets($fp, 2048)) === "\r\n" && $res === null )                                               //当读取全部头后
                    {
                        $res['header'] = join($receive);
                        $receive = null;
                    }
                }
                isset($res['header']) || $res['header'] = '';                                                                       //兼容GFW屏蔽地址
                $res['response'] = empty($receive) ? '' : join($receive);                                                           //响应信息
                if( strpos($res['header'], 'Transfer-Encoding: chunked') !== false )                                                //chunk传输
                {
                    $res['response'] = &self::dechunk($res['response']);                                                            //chunk还原
                }

                if( preg_match('/.* (\d+) .*/', $res['header'], $temp) && $temp[1] < '400')                                         //请求成功
                {
                    preg_match_all('@^Set-Cookie: ([^=\s]+)=(.*?)(?:; .*|$)$@m', $res['header'], $match, PREG_SET_ORDER);           //提取cookie
                    foreach($match as &$v) {
                        preg_match('@expires=(.*?)(?:; |$)@', $v[0], $v['expires']);
                        preg_match('@path=(.*?)(?:; |$)@', $v[0], $v['path']);
                        preg_match('@domain=(.*?)(?:; |$)@', $v[0], $v['domain']);

                        self::cookie(array(                                                                                         //记忆cookie
                                'domain'  => isset($v['domain'][1]) ? $v['domain'][1] : $data['url']['host'],
                                'path'    => isset($v['path'][1]) ?
                                        trim($v['path'][1]) : self::realpath('/' . $data['url']['path'] . 'a/../'),
                                'name'    => &$v[1],
                                'value'   => &$v[2],
                                'expires' => &$v['expires'][1],
                                'encode'  => false
                            ));
                    }
                    $res['state'] = true;
                } else {                                                                                                            //状态码错误
                    $res = array('state' => false, 'errno' => &$temp[1], 'errstr' => &$res['response']);
                }
            } else {                                                                                                                //开始二次请求
                $res['state'] = true;
            }

            fclose($fp);                                                                                                            //关闭连接
        } else {                                                                                                                    //连接失败
            $res = array('state' => false, 'errno' => &$errno, 'errstr' => &$errstr);                                               //状态,内容
        }

        return $res;
    }

    /**
     * 描述 : 还原chunk数据
     * 参数 :
     *     &str : 指定解码字符串
     * 返回 :
     *      返回还原的数据,失败返回false
     * 作者 : Edgar.lee
     */
    public static function &dechunk(&$str) {
        $eol = "\r\n";
        $offset = 0;                                                                        //当前偏移量
        $result = false;                                                                    //返回结果集

        while( ($nowPos = strpos($str, $eol, $offset)) !== false ) {
            if( is_numeric($len = hexdec(substr($str, $offset, $nowPos - $offset))) )       //有效数字
            {
                $result[] = substr($str, $nowPos + 2, $len);
                $offset = $len + $nowPos + 2;                                               //更新偏移量
            } else {                                                                        //解析出错
                $result = false;                                                            //解码失败
                break;
            }
        }

        is_array($result) && $result = join($result);
        return $result;
    }

    /**
     * 描述 : 设置读取请求站的cookie
     * 参数 :
     *      config : {
     *          "domain" :*指定权限域名
     *          "path"   :*指定有效路径
     *          "name"   : empty=读取有效cookie, 字符串=设置cookie
     *          "value"  : null=删除name, 字符串=设置name
     *          "expire" : empty(默认)=不过期, value的过期时间格式
     *          "encode" : 是否对value进行 RFC 1738 编码,默认=true
     *      }
     * 返回 :
     *
     * 注明 :
     *      列表结构(cookie) : {
     *          反转的域名 : {
     *              有效路径 : {
     *                  cookie键 : {
     *                      "value"  : cookie值
     *                      "expire" : 过期时间戳
     *                  }
     *              }
     *          }
     *      }
     * 作者 : Edgar.lee
     */
    public static function cookie($config) {
        $cookie = &self::$cookie;                                                                                       //引用cookie

        if( is_array($config) && isset($config['domain']) && isset($config['path']) ) {                                 //是数组 && 域名有效 && 路径有效
            $domain = strrev($config['domain']);                                                                        //反转域名
            $path   = substr($config['path'], -1) === '/' ? $config['path'] : $config['path'] . '/';                    //路径格式化

            if( empty($config['name']) ) {                                                                              //读取 cookie
                $time = time();                                                                                         //当期时间戳
                $result = array();                                                                                      //有效数据
                ksort($cookie);                                                                                         //达到根域在上子域在下的效果

                foreach($cookie as $kd => &$vd) {
                    if( strncasecmp($kd, $domain, strlen($kd)) === 0 ) {                                                //有效域名
                        ksort($vd);                                                                                     //根路径在上,子路径在下

                        foreach($vd as $kp => &$vp) {
                            if( strncasecmp($kp, $path, strlen($kp)) === 0 ) {                                          //读取根路径数据
                                foreach($vp as $k => &$v) {
                                    if( is_numeric($v['expire']) && $v['expire'] < $time ) {                            //数据过期
                                        unset($cookie[$domain][$kp][$k]);
                                    } else {
                                        $result[$k] = $k .'='. $v['value'];
                                    }
                                }
                            }
                        }
                    }
                }

                return join('; ', $result);
            } else if( isset($config['value']) ) {                                                                      //设置 cookie
                $cookie[$domain][$path][$config['name']] = array(
                    'value'  => !isset($config['encode']) || $config['encode'] ?                                        //编码 value
                            urlencode($config['value']) : $config['value'],
                    'expire' => empty($config['expire']) ?                                                              //时间戳
                            false : (int)(is_numeric($config['expire']) ? $config['expire'] : strtotime($config['expire']))
                );
            } else {                                                                                                    //删除 cookie
                unset($cookie[$domain][$path][$config['name']]);
            }
        }
    }

    /**
     * 描述 : 获取真实路径,去掉多余的'/./', '/../', '//'
     * 参数 :
     *      path : 指定源路径
     * 返回 :
     *      返回规范化的绝对路径名
     * 作者 : Edgar.lee
     */
    public static function realpath($path) {
        $suppress = array();                                                                                            //压制数据

        if( preg_match('@^(\w+:/{2,}[^/#?]*/|\w+:/)?([^?#]*)(.*)$@', $path, $match) )
        {
            $path = $match[2];
            unset($match[0]);
        }

        if( $path )
        {
            $each = explode('/', $path = strtr($path, '\\', '/'));                                                      //按'/'和'\'分组

            foreach($each as &$v)                                                                                       //压制过滤
            {
                if($v === '..')                                                                                         //如果为后退(../)结构
                {
                    if( ($temp = count($suppress)) > 0)                                                                 //如果压制包里有数据
                    {
                        if( $suppress[$temp - 1] === '..' )                                                             //并且最后一个为后退结构
                        {
                            $suppress[] = '..';                                                                         //压入当前后退结构
                        } else {                                                                                        //否则
                            array_pop($suppress);                                                                       //弹出压制中最后一结构
                        }
                    } else {                                                                                            //压制包中无数据
                        $suppress[] = '..';                                                                             //压入当前后退结构
                    }
                } else if($v !== '' && $v !== '.') {                                                                    //如果不为当前结构(./或'')
                    $suppress[] = $v;                                                                                   //压入常规结构
                }
            }

            $temp = array(                                                                                              //规则整合
                $path[0] === '/' ? '/' : '',                                                                            //头标识
                $path[strlen($path) - 1] === '/' ? '/' : ''                                                             //尾标识
            );
            if( $suppress = join('/', $suppress) )                                                                      //整理后不为''
            {
                $match[2] = $temp[0] . $suppress . $temp[1];                                                            //返回 头标识+整理结果+尾标识
            } else {                                                                                                    //整理后为''
                $match[2] = $temp[0] === $temp[1] ? ($temp[0] === '' ? '.' : '/') : '';                                 //头尾标识相同 ? (都为'' ? '.' : '/') : ''
            }
        } else {
            $match[2] = '.';
        }

        return join($match);
    }

    /**
     * 描述 : 文本加密与解密
     * 参数 :
     *      key    : 加密解密文本密码
     *      txt    : 需要加解密的文本
     *      base64 : 加密解密标识,null(默认)=无编码加密解密, true=base64加密,false=base64解密
     *      level  : 加密级别 1=简单线性加密, >1 = RC4加密数字越大越安全越慢, 默认=256
     * 返回 :
     *      加密或解密后的明码字符串
     * 演示 :
     *      rc4('密码', '测试', true);
     *      随机加密字符,如:Yegw4WXcMOth/DvC
     *      rc4('密码', rc4('密码', '测试', true), false);
     *      测试
     * 作者 : Edgar.lee
     */
    public static function rc4($pwd, $txt, $base64 = null, $level = 256) {
        $base64 === false && $txt = base64_decode($txt);                                                                //base64解密
        $result = '';
        $kL = strlen($pwd);
        $tL = strlen($txt);

        if( $level > 1 ) {                                                                                              //非线性加密
            for ($i = 0; $i < $level; ++$i) {
                $key[$i] = ord($pwd[$i % $kL]);
                $box[$i] = $i;
            }

            for ($j = $i = 0; $i < $level; ++$i) {
                $j = ($j + $box[$i] + $key[$i]) % $level;
                $tmp = $box[$i];
                $box[$i] = $box[$j];
                $box[$j] = $tmp;
            }

            for ($a = $j = $i = 0; $i < $tL; ++$i) {
                $a = ($a + 1) % $level;
                $j = ($j + $box[$a]) % $level;

                $tmp = $box[$a];
                $box[$a] = $box[$j];
                $box[$j] = $tmp;

                $k = $box[($box[$a] + $box[$j]) % $level];
                $result .= chr(ord($txt[$i]) ^ $k);
            }
        } else {                                                                                                        //简单线性加密
            for($i = 0; $i < $tL; ++$i) {
                $result .= $txt[$i] ^ $pwd[$i % $kL];
            }
        }

        $base64 && $result = base64_encode($result);                                                                    //base64加密
        return $result;
    }

    /*
     * 描述 : 深度加/删反斜杠
     * 参数 :
     *     &data : 指定替换的数组
     *      func : addslashes(默认)=添加反斜杠, stripslashes=删除反斜杠
     * 作者 : Edgar.lee
     */
    public static function &slashesDeep(&$data, $func = 'addslashes') {
        $waitList = array(&$data);                                                                                      //待处理列表

        do {
            $wk = key($waitList);
            $wv = &$waitList[$wk];
            unset($waitList[$wk]);

            if( is_array($wv) ) {
                $result = array();                                                                                      //结果列表
                foreach($wv as $k => &$v) {
                    $result[$func($k)] = &$v;
                    $waitList[] = &$v;
                }
                $wv = $result;
            } else if( is_string($wv) ) {
                $wv = $func($wv);
            }
        } while( !empty($waitList) );

        return $data;
    }
}

uc_api_system::init();